/**
	License:			Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:			OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:				ParticleAnimator.d
	Description:		particle animators
	Date:				07.01.2008 by Jurec
*/
module moonglide.scenes.animators.ParticleAnimator;

import
	tango.math.Math,
	moonglide.math.MoonMath,

	moonglide.core.Events,
	moonglide.core.engobjects.EngObject,
	moonglide.core.engobjects.EngParSystem;



//=============================================================
/// Simple particle animator
//=============================================================
class CSimplParAnimator : IAnimator
{
	mixin TEventGenerator;

	protected:
		int[] _delPar;
		int	  _countDel		= 0;
		float _rateCounter	= 0;

		void _DefaultInitParticle(out SEngParticle Par)
		{
			with( Par )
			{
				speedNorm = mrandom( parent.floats["minspeed"], parent.floats["maxspeed"] );

				if( parent.rendType == eparsysrendertype.sprites_rot )
				{
					angle		=	mrandom(-2*PI,2*PI)/10;
					deltaAngle	=	mrandom(-0.02,0.02)/mrandom(1,10) ;
					getMat2f(angle, rotation);
				}

				size = mrandom(parent.floats["minsize"], parent.floats["maxsize"]);
				changeSize = 0;
				life = ( parent.bools["lifedown"] ) ? parent.floats["lifetime"] : 1.0f;

				float halfSpr;
				position = parent.wposition;

				if( parent.floats["posspread"] != 0.0f )
				{
					halfSpr = parent.floats["posspread"] / 2;
					position.x += mrandom(0.0f,1.0f) * parent.floats["posspread"] - halfSpr;
					position.y += mrandom(0.0f,1.0f) * parent.floats["posspread"] - halfSpr;
					position.z += mrandom(0.0f,1.0f) * parent.floats["posspread"] - halfSpr;
				}

                Matrix33f abc = parent.worientation;
                abc.transpose;
				velocity = abc * parent.vector;

				if( parent.floats["spread"] != 0.0f )
				{
					halfSpr = parent.floats["spread"] / 2;
					velocity.x += mrandom(0.0f,1.0f) * parent.floats["spread"] - halfSpr;
					velocity.y += mrandom(0.0f,1.0f) * parent.floats["spread"] - halfSpr;
					velocity.z += mrandom(0.0f,1.0f) * parent.floats["spread"] - halfSpr;
				}

				velocity.normalize();
				velocity *= speedNorm;

				acceleration.set(0.0f,0,0.0f);
				color.set(1.0f,1.0f,1.0f,1.0f);
				changeCol.set(parent.floats["changeR"] ,parent.floats["changeG"], parent.floats["changeB"], parent.floats["changeA"]);
			}
		}

		void _DefaultUpdateParticle( inout SEngParticle Par, in float time )
		{
			with( Par )
			{
				if( parent.bools["lifedown"] )
				{
					life -= time;
					if( life <= 0 )
						return;
				}

				if( parent.bools["killdist"] )
				{
					if( ( position - parent.wposition ).norm() > parent.floats["killdist"] )
					{
						life = 0;
						return;
					}
				}

				size += changeSize * time;

				if(( size <= 0 ) && ( changeSize < 0 ))
				{
					life = 0;
					return;
				}

				time		*= 100;
				velocity	+= acceleration * time;
				position	+= velocity * time;
				color		+= changeCol * time;

				if( parent.rendType == eparsysrendertype.sprites_rot )
				{
					angle+=deltaAngle;
					getMat2f(angle, rotation);
				}
			}
		}


		bool _DefaultDoNeedToKillParticle( inout SEngParticle Par )
		{
			return( Par.life <= 0 );
		}

	public:
		void delegate(out SEngParticle Par) xInitPar;
		bool delegate(inout SEngParticle Par) xIsNeedToKillPar;

		CEngParSystem	parent;

		void animate( float time )
		{
			// NOTE (digited#): let's try to speed up animators - sun and spaceship act strange
			time *= 1000;

			_rateCounter += parent.floats["rate"] * time;

			time *= 0.01;

			for( int i=cast(int)_rateCounter; i>0; --i )
			{
				parent.addParticle();
			}
		//	debug debugLog.trace( tconverti.toString(parent.parCount) );

			if( _rateCounter >= 1 )
				_rateCounter = _rateCounter - cast(int)_rateCounter;

			if( parent.parCount <= 0 )
				return;

			_delPar.length = 0;

			for( int ii = 0; ii<parent.parCount; ++ii )
			{
				_DefaultUpdateParticle( parent.particles[ii], time );

				if( xIsNeedToKillPar( parent.particles[ii] ))
					_delPar ~= ii;
			}

			foreach( int i; _delPar )
				parent.delParticle(i);
		}

		//================================================================
		/// callback: getting todo-data
		//================================================================
		void onEventCallback( CEvent event, IEventGenerator gen )
		{
			//
		}

		void onEventReceive( CEvent event, IEventSender sender )
		{
			//
		}

		//================================================================
		/// constructor & destructor
		//================================================================
		this( CEngParSystem obj )
		{
			parent = obj;
			_id = parent.id ~ "_fmanim";

			_rateCounter = 0;
			xInitPar = &_DefaultInitParticle;
			xIsNeedToKillPar = &_DefaultDoNeedToKillParticle;
			parent.initPar = xInitPar;
		}

		~this()
		{
			parent = null;
		}
}


struct SFlowPoint
{
	Vector3f pos;
	CEngObject Obj;
	void delegate (CFlowParAnimator callback, SFlowPoint* parent) OnParPass;
}

SFlowPoint FVector3f(float xpos,float ypos,float zpos, CEngObject xObj = null)
{
	SFlowPoint res;
	res.Obj = xObj;
	res.pos.set(xpos,ypos,zpos);
	res.OnParPass = null;
	return res;
}

//=============================================================
///	Particle Flow animator
///  Used for animate particles by curve
//=============================================================
class CFlowParAnimator : CSimplParAnimator
{
	protected:
		void _DefaultInitParticle(out SEngParticle Par)
		{
			with( Par )
			{
				if( parent.rendType == eparsysrendertype.sprites_rot )
				{
					angle		=	mrandom(-2*PI,2*PI)/10;
					deltaAngle	=	mrandom(-0.1,0.1)/mrandom(1,10);
					getMat2f(angle, rotation);
				}

				if (_AimExists(1))
				{
					aim	=	1; // aim for new particle flowArray[1]
					velocity = flowArray[1].pos - flowArray[0].pos;
				}
				else
					aim	= 0; // not aim

				size = mrandom(parent.floats["minsize"], parent.floats["maxsize"]);
				changeSize = 0;
				life = 1000.0f;
				position = parent.wposition;
				speedNorm = mrandom(parent.floats["minspeed"], parent.floats["maxspeed"]);
	/*
				if (parent.floats["spread"] != 0.0f)
				{
					float halfSpr = parent.floats["spread"] / 2;
					velocity.x += mrandom(0.0f,1.0f) * parent.floats["spread"] - halfSpr;
					velocity.y += mrandom(0.0f,1.0f) * parent.floats["spread"] - halfSpr;
					velocity.z += mrandom(0.0f,1.0f) * parent.floats["spread"] - halfSpr;
				}
				velocity.normalize();
				velocity *= mrandom(parent.floats["minspeed"], parent.floats["maxspeed"]);
	*/
				acceleration.set(0.0f,0,0.0f);
				color.set(1.0f,1.0f,1.0f,1.0f);
				changeCol.set(0.0f,0.0f,0.0f,0.0f);
			}
		}


		void _DefaultUpdateParticle(inout SEngParticle Par, in float time)
		{
			with( Par )
			{
				life-= time;

				if( life <= 0 )
					return;

				size += changeSize * time;

				time*=100;

				velocity = (flowArray[aim].pos - position);
				float dist = velocity.norm;
				velocity *= speedNorm*time/dist;// normalize * speedNorm*time

				if( speedNorm * time > dist )
				{
					// aim reached
					if(( aim == 0 ) && ( loopReachStartDie ))
					{
						life = 0;
						return;
					}

					position = flowArray[aim].pos;

					if( flowArray[aim].OnParPass !is null )
						flowArray[aim].OnParPass( this, &flowArray[aim] );

					aim++;

					bool aimex = _AimExists(aim);

					if( !aimex )
						if( !loopFlow )
						{
							life = 0;
							return;
						}
						else
							aim = 0;

					velocity = (flowArray[aim].pos - position);
					velocity.normalize;
					velocity *= (speedNorm*time - dist);

				}

				position += velocity;
				color += changeCol * time;

				if( parent.rendType == eparsysrendertype.sprites_rot )
				{
					angle+=deltaAngle;
					getMat2f(angle, rotation);
				}
			}
		}


		bool _DefaultDoNeedToKillParticle(inout SEngParticle Par)
		{
			return (Par.life<=0);
		}


		bool _AimExists(in int xAim)
		{
			return (flowArray.length>xAim);
		}

	public:
		// Flow Points in world space. flowArray[0].pos - always emmiter position
		SFlowPoint[]	flowArray;

		bool			loopFlow			= false,
						loopReachStartDie	= true;

		void animate( float time )
		{
			time *= 1000;

			flowArray[0].pos = parent.wposition;
			time *= 10;

			///parent.vector = parent.worientation * parent.vector;

			_rateCounter += parent.floats["rate"] * time;

			if (_rateCounter>1)
			{
				parent.addParticle();	// only <=1 particle by 'animate' can call
				_rateCounter = 0;
			}


			if (parent.parCount<=0) return;

			_delPar.length = 0;

			for( int ii = 0; ii<parent.parCount; ++ii )
			{
				_DefaultUpdateParticle( parent.particles[ii], time );

				if ( xIsNeedToKillPar( parent.particles[ii] ) )
					_delPar ~= ii;
			}

			foreach( int i; _delPar )
				parent.delParticle(i);
		}

		//================================================================
		/// constructor & destructor
		//================================================================
		this( CEngParSystem obj )
		{
			super(obj);

			_id = parent.id ~ "_fmflowanim";

			xInitPar = &_DefaultInitParticle;
			xIsNeedToKillPar = &_DefaultDoNeedToKillParticle;
			parent.initPar = xInitPar;
			flowArray.length = 1;
		}

		~this()
		{
			flowArray = null;
		}

	}


//=============================================================
///	Particle Flow animator: Used for animate particles by curve
//=============================================================
class CSPECFlowParAnimator : CFlowParAnimator
{
	protected:
		void _DefaultInitParticle( out SEngParticle Par )
		{
			super._DefaultInitParticle(Par);

			Par.color.x = ( _endRate / _sendRate );
			Par.color.y = ( _endRate / _sendRate );
			Par.color.z = ( _endRate / _sendRate );
			Par.color.w = ( _endRate / _sendRate ) /1.2;
		}


		void _DefaultUpdateParticle( inout SEngParticle Par, float time )
		{
			super._DefaultUpdateParticle( Par, time );
		}


		bool _DefaultDoNeedToKillParticle( inout SEngParticle Par )
		{
			return( Par.life <= 0 );
		}

		float	_sendRate,
				_endRate,
				_waitEndRate,
				_rate;
		bool	_wait;

	public:
		void InitRate()
		{
			_sendRate = _endRate = mrandom(10,200);
			_waitEndRate = mrandom(10,200);
		}

		void animate( float time )
		{
			time *= 1000;

			if( _wait )
			{
				_waitEndRate -= time*10;

				if(( _waitEndRate <= 0) && ( parent.parCount <= 0 ))
				{
					parent.floats["rate"] = _rate;
					_wait = false;
					InitRate();
				}
			}
			else
			{
				_endRate -= time*10;

				if( _endRate <= 0 )
				{
					super.animate(time);
					_rate = parent.floats["rate"];
					parent.floats["rate"] = 0;
					_wait = true;
					InitRate();
				}
			}

			super.animate(time);
		}


		//================================================================
		/// constructor & destructor
		//================================================================
		this( CEngParSystem obj )
		{
			super(obj);

			_id = parent.id ~ "_fmSflowanim";

			xInitPar = &_DefaultInitParticle;
			xIsNeedToKillPar = &_DefaultDoNeedToKillParticle;
			parent.initPar = xInitPar;
			flowArray.length = 1;
			InitRate();
		}

		~this()
		{

		}
}
